本系列文已改編成書「甚麼?網頁也可以做派對遊戲?使用 Vue 和 babylon.js 打造 3D 派對遊戲吧!」
書中不只重構了程式架構、改善了介面設計,還新增了 2 個新遊戲呦!ˋ( ° ▽、° )
新遊戲分別使用了陀螺儀與震動回饋,趕快買書來研究研究吧!ლ(╹∀╹ლ)
在此感謝深智數位的協助,歡迎大家前往購書,鱈魚感謝大家 (。・∀・)。
助教:「所以到底差在哪啊?沒圖沒真相,被你坑了都不知道。(´。_。`)」
鱈魚:「你對我是不是有甚麼很深的偏見啊 (っ °Д °;)っ,來人啊,上連結!」
要讓很多個多邊形出現,第一步就是建立一個產生多邊形的 function(這一句好像廢話 ¯_( ͡° ͜ʖ ͡°)_/¯)
先來定義一下多邊形需要的參數。
import PolygonBase, { FillType, ShapeType } from './polygon-base.vue';
...
interface PolygonParams {
left: string;
top: string;
size: string;
rotate: string;
opacity: string | number;
shape: `${ShapeType}`;
fill: `${FillType}`;
color: string;
animationDuration: string;
}
可以注意到有一個項目是 animationDuration,這是因為我們預期使用 CSS 之 animation 讓多邊形動起來,透過讓每個多邊形之 animationDuration 都不同,就可以有交錯漂浮的效果,讓畫面更多元。
接著新增名為 createPolygonParams 的 function,負責產生多邊形參數。
import { random, sample } from 'lodash-es';
...
function createPolygonParams() {
// 變暗、偏移色相、提升飽和度,作為 polygon 候選顏色
const darkColor = lighten(props.mainColor, -10);
const hsvColor = rgbToHsv(textToRgb(darkColor));
hsvColor.s += 20;
const hsvColor01 = cloneDeep(hsvColor);
hsvColor01.h -= 10;
const hsvColor02 = cloneDeep(hsvColor);
hsvColor02.h += 10;
const colors = [
rgbToHex(hsvToRgb(hsvColor)),
rgbToHex(hsvToRgb(hsvColor01)),
rgbToHex(hsvToRgb(hsvColor02))
];
const params: PolygonParams = {
left: `${random(0, 100)}%`,
top: `${random(0, 100)}%`,
size: `${random(5, 20)}rem`,
rotate: `${random(0, 90)}deg`,
opacity: random(0.1, 0.2, true),
color: sample(colors) ?? '',
shape: sample(Object.values(ShapeType)) ?? 'round',
fill: sample(Object.values(FillType)) ?? 'solid',
animationDuration: `${random(5, 20)}s`,
}
return params;
}
這裡引入了 lodash 的 random、sample 與 cloneDeep,功能分別為:
接著就是實際產生多邊形的時候了,一般來說最常見的方式就是用一個矩陣將 PolygonParams 裝起來,並在 template 透過 v-for 產生即可,不過這樣有個小問題。
當多邊形動畫結束時,要移除矩陣中指定的多邊形會稍微有點麻煩,這裡用一個更簡單的方式實作,也就是 JavaScript 的 Map 物件。
想要深入了解的讀者可以參考此連結:JavaScript - Map
其實使用 object 也可以,只是 Map 提供更簡便直覺的 Method,用來新增、刪除內容。
首先定義一個儲存多邊形用的 Map 物件。
const polygonsMap = ref<Map<string, PolygonParams>>(new Map());
並新增用來新增、刪除多邊形的 function。
import { nanoid } from 'nanoid';
...
function addPolygon(params: PolygonParams) {
polygonsMap.value.set(nanoid(), params);
}
function removePolygon(id: string) {
polygonsMap.value.delete(id);
}
這裡透過 nanoid 產生 key。
nanoid 是一種尺寸小、速度快的唯一字串生成套件,簡單好用。
接著來實作持續產生多邊形效果,有以下需求:
let timer: ReturnType<typeof setInterval>;
/** 若 generateInterval 參數有變化,則自動重新建立 timer */
watch(() => props.generateInterval, (generateInterval) => {
clearInterval(timer);
timer = setInterval(async () => {
// 到達最大數量後,停止生成
if (polygonsMap.value.size >= props.maxQuantity) return;
// 刻意延遲隨機時間,讓多邊形生成不會過度固定而顯得死板
await promiseTimeout(random(300, 1000));
addPolygon(createPolygonParams());
}, generateInterval);
}, {
immediate: true
});
function init() {
// 預先建立多邊錫
for (let i = 0; i < props.initialQuantity; i++) {
addPolygon(createPolygonParams());
}
}
init();
onBeforeUnmount(() => {
clearInterval(timer);
});
現在讓我們打開 Vue DevTools。
可以看到 polygonsMap 內應該會有多邊形了。
讓我們將 polygonsMap 加到 template 裡面吧!將原本測試用的多邊形刪除,改為以下程式。
<template>
<div
class="overflow-hidden"
:style="backgroundStyle"
>
<slot />
<polygon-base
v-for="[key, polygon] in polygonsMap"
:key="key"
class="absolute"
:style="`left: ${polygon.left}; top: ${polygon.top}; animation-duration: ${polygon.animationDuration}`"
:color="polygon.color"
:rotate="polygon.rotate"
:shape="polygon.shape"
:fill="polygon.fill"
:size="polygon.size"
:opacity="polygon.opacity"
@animationend="removePolygon(key)"
/>
</div>
</template>
讀者可能會注意到在此註冊了 **@**animationend
事件,這個事件用於 CSS 之 animation 播放結束時觸發,很適合用來刪除元素,所以將 removePolygon 綁定在此事件。
同時也加入 slot,讓父元件額外加入內容的彈性。
現在可以看到多邊形出現了。(・∀・)ノ
最後我們讓多邊形動起來吧,實作相當簡單,建立動畫用的 class 就行惹。
<style scoped lang="sass">
.polygon-move
animation: polygon-move 10s forwards linear
@keyframes polygon-move
0%
transform: translate(0px, 0px) rotate(0deg)
opacity: 0
10%
opacity: 1
90%
opacity: 1
100%
transform: translate(-10rem, 10rem) rotate(6deg)
opacity: 0
</style>
把 class 加入 template 中。
<template>
<div
...
>
...
<polygon-base
...
class="absolute polygon-move"
...
/>
</div>
</template>
可以看到多邊形飄起來了!(。^▽^)
不過這樣畫面看起來稍嫌單調,讓我們新增幾個背景用的多邊形,這個時候剛剛預留的 slot 就派上用場了!
在 the-home 中的 background-polygons-floating 加入兩個多邊形並新增對應樣式。
<template>
<background-polygons-floating class="absolute inset-0">
<polygon-base
class="bg-polygon-lt"
size="50rem"
fill="fence"
shape="square"
rotate="30deg"
opacity="0.1"
color="#e09c48"
/>
<polygon-base
class="bg-polygon-rb"
size="80rem"
fill="spot"
shape="round"
rotate="30deg"
opacity="0.1"
color="#f0a53c"
/>
</background-polygons-floating>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import BackgroundPolygonsFloating from '../components/background-polygons-floating.vue';
import PolygonBase from '../components/polygon-base.vue';
</script>
<style scoped lang="sass">
.bg-polygon-lt
position: absolute
left: -25rem
top: -25rem
animation: polygon-swing 40s infinite ease-in-out
.bg-polygon-rb
position: absolute
right: -25rem
bottom: -50rem
transform: translate(30%, 30%)
animation: polygon-swing 28s infinite ease-in-out
@keyframes polygon-swing
0%, 100%
transform: rotate(0deg)
50%
transform: rotate(20deg)
</style>
現在左上角和右下角各有一個固定班底的多邊形了!( •̀ ω •́ )y
下一章讓我們加上按鈕,讓使用者「建立派對」或「加入遊戲」吧。( •̀ ω •́ )✧
以上程式碼已同步至 GitLab,大家可以前往下載: